/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package 解析.node.node;

import 解析.node.others.TreeInfo;
import 解析.Matcher;
import static 解析.Pattern.GREEDY;
import static 解析.Pattern.LAZY;

/**
 *
 * @author yanxi
 */
public class GroupCurly extends Node {

    public Node atom;
    public int type;
    public int cmin;
    public int cmax;
    public int localIndex;
    public int groupIndex;
    public boolean capture;

    public GroupCurly(Node node, int cmin, int cmax, int type, int local, int group, boolean capture) {
        this.atom = node;
        this.type = type;
        this.cmin = cmin;
        this.cmax = cmax;
        this.localIndex = local;
        this.groupIndex = group;
        this.capture = capture;
        getChildren().add(node); 
    }
    
    @Override
    public boolean match(Matcher matcher, int i, CharSequence seq) {
        int[] groups = matcher.groups;
        int[] locals = matcher.locals;
        int save0 = locals[localIndex];
        int save1 = 0;
        int save2 = 0;

        if (capture) {
            save1 = groups[groupIndex];
            save2 = groups[groupIndex + 1];
        }

        // Notify GroupTail there is no need to setup group info
        // because it will be set here
        locals[localIndex] = -1;

        boolean ret = true;
        for (int j = 0; j < cmin; j++) {
            if (atom.match(matcher, i, seq)) {
                if (capture) {
                    groups[groupIndex] = i;
                    groups[groupIndex + 1] = matcher.last;
                }
                i = matcher.last;
            } else {
                ret = false;
                break;
            }
        }
        if (ret) {
            if (type == GREEDY) {
                ret = match0(matcher, i, cmin, seq);
            } else if (type == LAZY) {
                ret = match1(matcher, i, cmin, seq);
            } else {
                ret = match2(matcher, i, cmin, seq);
            }
        }
        if (!ret) {
            locals[localIndex] = save0;
            if (capture) {
                groups[groupIndex] = save1;
                groups[groupIndex + 1] = save2;
            }
        }
        return ret;
    }

    // Aggressive group match
    boolean match0(Matcher matcher, int i, int j, CharSequence seq) {
        // don't back off passing the starting "j"
        int min = j;
        int[] groups = matcher.groups;
        int save0 = 0;
        int save1 = 0;
        if (capture) {
            save0 = groups[groupIndex];
            save1 = groups[groupIndex + 1];
        }
        for (;;) {
            if (j >= cmax) {
                break;
            }
            if (!atom.match(matcher, i, seq)) {
                break;
            }
            int k = matcher.last - i;
            if (k <= 0) {
                if (capture) {
                    groups[groupIndex] = i;
                    groups[groupIndex + 1] = i + k;
                }
                i = i + k;
                break;
            }
            for (;;) {
                if (capture) {
                    groups[groupIndex] = i;
                    groups[groupIndex + 1] = i + k;
                }
                i = i + k;
                if (++j >= cmax) {
                    break;
                }
                if (!atom.match(matcher, i, seq)) {
                    break;
                }
                if (i + k != matcher.last) {
                    if (match0(matcher, i, j, seq)) {
                        return true;
                    }
                    break;
                }
            }
            while (j > min) {
                if (next.match(matcher, i, seq)) {
                    if (capture) {
                        groups[groupIndex + 1] = i;
                        groups[groupIndex] = i - k;
                    }
                    return true;
                }
                // backing off
                i = i - k;
                if (capture) {
                    groups[groupIndex + 1] = i;
                    groups[groupIndex] = i - k;
                }
                j--;

            }
            break;
        }
        if (capture) {
            groups[groupIndex] = save0;
            groups[groupIndex + 1] = save1;
        }
        return next.match(matcher, i, seq);
    }

    // Reluctant matching
    boolean match1(Matcher matcher, int i, int j, CharSequence seq) {
        for (;;) {
            if (next.match(matcher, i, seq)) {
                return true;
            }
            if (j >= cmax) {
                return false;
            }
            if (!atom.match(matcher, i, seq)) {
                return false;
            }
            if (i == matcher.last) {
                return false;
            }
            if (capture) {
                matcher.groups[groupIndex] = i;
                matcher.groups[groupIndex + 1] = matcher.last;
            }
            i = matcher.last;
            j++;
        }
    }

    // Possessive matching
    boolean match2(Matcher matcher, int i, int j, CharSequence seq) {
        for (; j < cmax; j++) {
            if (!atom.match(matcher, i, seq)) {
                break;
            }
            if (capture) {
                matcher.groups[groupIndex] = i;
                matcher.groups[groupIndex + 1] = matcher.last;
            }
            if (i == matcher.last) {
                break;
            }
            i = matcher.last;
        }
        return next.match(matcher, i, seq);
    }

    @Override
    public boolean study(TreeInfo info) {
        // Save original info
        int minL = info.minLength;
        int maxL = info.maxLength;
        boolean maxV = info.maxValid;
        boolean detm = info.deterministic;
        info.reset();

        atom.study(info);

        int temp = info.minLength * cmin + minL;
        if (temp < minL) {
            temp = 0xFFFFFFF; // Arbitrary large number
        }
        info.minLength = temp;

        if (maxV & info.maxValid) {
            temp = info.maxLength * cmax + maxL;
            info.maxLength = temp;
            if (temp < maxL) {
                info.maxValid = false;
            }
        } else {
            info.maxValid = false;
        }

        if (info.deterministic && cmin == cmax) {
            info.deterministic = detm;
        } else {
            info.deterministic = false;
        }
        return next.study(info);
    } 
    
    @Override
    public String toString(){
        /*
        return "["+this.getClass().getName()+"]"+"type:"+type+",cmin:"+cmin+",cmax:"+cmax+
                ",localindex:"+localIndex+",groupIndex:"+groupIndex+",capture:"+capture;*/
        //return "重复分组[id:"+groupIndex+"]"+"type:"+type+",cmin:"+cmin+",cmax:"+cmax+",capture:"+capture;
        String s="";
        if(capture)s+="(";
        else s+="(?:";
        s+=atom.toString();
        s+=")";
        if(cmin==0&&cmax==1){
            s+="?";
        }
        else if(cmax==2147483647){
            if(cmin==0)s+="*";
            else if(cmin==1)s+="+";
            else s+="{"+cmin+",}";
        }
        else{
            if(cmin==cmax)s+="{"+cmin+"}";
            else s+="{"+cmin+","+cmax+"}";
        }
        
        if(type==1)s+="?";
        else if(type==2)s+="+";
        return s;
    }
}
